package utils

import (
	"container/list"
	"context"
	"crypto/sha1"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var httpClientValue atomic.Value
var RequestRedirectHeader = "rd"

func init() {
	httpClientValue.Store(&http.Client{CheckRedirect: redirectCheck})
}

func GetHttpClient() *http.Client {
	return httpClientValue.Load().(*http.Client)
}

func SetHttpClient(httpClient *http.Client) {
	httpClient.CheckRedirect = redirectCheck
	httpClientValue.Store(httpClient)
}

func redirectCheck(req *http.Request, via []*http.Request) error {
	if req.Header.Get(RequestRedirectHeader) != "" {
		return http.ErrUseLastResponse
	}
	if len(via) >= 10 {
		return errors.New("stopped after 10 redirects")
	}
	return nil
}

func CheckPathAndCreateParent(path string, isFile bool) (bool, error) {
	parentDir := filepath.Dir(path)
	if stat, err := os.Stat(parentDir); err != nil {
		if os.IsNotExist(err) {
			if err = os.MkdirAll(parentDir, os.ModeDir); err != nil {
				return false, fmt.Errorf("failed to create parent path: %w", err)
			}
		}
	} else if !stat.IsDir() {
		return false, fmt.Errorf("parent path is not a directory: %w", os.ErrInvalid)
	}
	if stat, err := os.Stat(path); err != nil {
		if os.IsNotExist(err) {
			if !isFile {
				err = os.MkdirAll(path, os.ModePerm)
				if err != nil {
					return false, fmt.Errorf("failed to create path: %w", err)
				} else {
					return true, nil
				}
			}
		} else {
			return false, err
		}
	} else if isFile && stat.IsDir() {
		return true, fmt.Errorf("path is a directory: %w", os.ErrInvalid)
	} else if !isFile && !stat.IsDir() {
		return true, fmt.Errorf("path is a file: %w", os.ErrInvalid)
	}
	return false, nil
}

func ShouldIgnoreError(err error) bool {
	if err == nil {
		return true
	}
	return errors.Is(err, context.DeadlineExceeded) ||
		errors.Is(err, context.Canceled) ||
		err.Error() == "context deadline exceeded (Client.Timeout or context cancellation while reading body)"
}

func JsonString(obj interface{}) string {
	marshal, err := json.Marshal(obj)
	if err != nil {
		zap.L().Fatal("json.Marshal failed", zap.Error(err))
	}
	return string(marshal)
}

func ConcatPath(p1, p2 string) string {
	if strings.HasSuffix(p1, "/") || strings.HasPrefix(p2, "/") {
		return p1 + p2
	}
	return p1 + "/" + p2
}

func ConvertToUnixPath(p string) string {
	if filepath.Separator == '/' {
		return p
	}
	return strings.Replace(p, "\\", "/", -1)
}

func IsValidUuid(v string) bool {
	_, err := uuid.Parse(v)
	return err == nil
}

func GetENV(key, defaultValue string) string {
	env := os.Getenv(key)
	if env == "" {
		return defaultValue
	}
	return env
}

func GetDir(p string) string {
	v := filepath.Dir(p)
	if filepath.Separator == '\\' {
		v = strings.Replace(v, "\\", "/", -1)
	}
	return v
}

type lockEntry struct {
	lock    sync.RWMutex
	counter int
}

type NamedRWLock struct {
	lockMap   map[string]*lockEntry
	queryLock sync.Mutex
}

func NewNamedRWLock() *NamedRWLock {
	return &NamedRWLock{
		lockMap: make(map[string]*lockEntry),
	}
}

func (l *NamedRWLock) getLock(name string) *lockEntry {
	l.queryLock.Lock()
	defer l.queryLock.Unlock()
	lock, ok := l.lockMap[name]
	if !ok {
		lock = new(lockEntry)
		l.lockMap[name] = lock
	}
	lock.counter++
	return lock
}

func (l *NamedRWLock) RLock(name string) func() {
	myLock := l.getLock(name)
	myLock.lock.RLock()
	one := new(sync.Once)
	return func() {
		one.Do(func() {
			myLock.lock.RUnlock()
			l.queryLock.Lock()
			defer l.queryLock.Unlock()
			myLock.counter--
			if myLock.counter == 0 {
				delete(l.lockMap, name)
			}
		})
	}
}

func (l *NamedRWLock) WLock(name string) func() {
	myLock := l.getLock(name)
	myLock.lock.Lock()
	one := new(sync.Once)
	return func() {
		one.Do(func() {
			myLock.lock.Unlock()
			l.queryLock.Lock()
			defer l.queryLock.Unlock()
			myLock.counter--
			if myLock.counter == 0 {
				delete(l.lockMap, name)
			}
		})
	}
}

func Sha1(data, dest []byte) []byte {
	s1 := sha1.New()
	s1.Write(data)
	return s1.Sum(dest)
}

func Sha1ForReader(reader io.Reader, dest []byte) ([]byte, error) {
	hash := sha1.New()
	_, err := io.Copy(hash, reader)
	if err != nil {
		return nil, err
	}
	return hash.Sum(dest), nil
}

func CopyBuffer(ctx context.Context, dst io.Writer, src io.Reader, buf []byte) (written int64, err error) {
	for ctx.Err() == nil {
		nr, er := src.Read(buf)
		if nr > 0 {
			nw, ew := dst.Write(buf[0:nr])
			if nw < 0 || nr < nw {
				nw = 0
				if ew == nil {
					ew = errors.New("invalid write result")
				}
			}
			written += int64(nw)
			if ew != nil {
				err = ew
				break
			}
			if nr != nw {
				err = io.ErrShortWrite
				break
			}
		}
		if er != nil {
			if er != io.EOF {
				err = er
			}
			break
		}
	}
	if err == nil {
		err = context.Cause(ctx)
		if err == nil {
			err = ctx.Err()
		}
	}
	return written, err
}

func GetCauseFromContext(ctx context.Context) error {
	err := context.Cause(ctx)
	if err == nil {
		err = ctx.Err()
	}
	return err
}

type SefaQueue struct {
	data   *list.List
	lock   sync.Mutex
	cond   *sync.Cond
	closed bool
}

func NewSefaQueue() *SefaQueue {
	queue := &SefaQueue{
		data: list.New(),
	}
	queue.cond = sync.NewCond(&queue.lock)
	return queue
}

func (q *SefaQueue) Close() {
	q.lock.Lock()
	defer q.lock.Unlock()
	q.closed = true
	q.cond.Broadcast()
}

func (q *SefaQueue) Push(v interface{}) {
	q.lock.Lock()
	defer q.lock.Unlock()
	q.data.PushBack(v)
	q.cond.Signal()
}

func (q *SefaQueue) Pop() interface{} {
	q.lock.Lock()
	defer q.lock.Unlock()
	if q.closed {
		return nil
	}
	if q.data.Len() == 0 {
		q.cond.Wait()
	}
	if q.closed {
		return nil
	}
	e := q.data.Front()
	if e != nil {
		q.data.Remove(e)
		return e.Value
	}
	return nil
}

func StreamForm(writer func(*multipart.Writer)) (*io.PipeReader, string) {
	r, w := io.Pipe()
	m := multipart.NewWriter(w)
	go func() {
		defer w.Close()
		defer m.Close()
		writer(m)
	}()
	return r, m.FormDataContentType()
}

type ReadAtWarp struct {
	io.ReadSeeker
	l sync.Mutex
}

func (r *ReadAtWarp) ReadAt(p []byte, off int64) (n int, err error) {
	r.l.Lock()
	defer r.l.Unlock()
	_, err = r.Seek(off, io.SeekStart)
	if err != nil {
		return
	}
	n, err = r.Read(p)
	return
}

func NewReadAtWarp(r io.ReadSeeker) *ReadAtWarp {
	return &ReadAtWarp{ReadSeeker: r}
}

type namedLockEntry struct {
	lock      sync.Mutex
	lockCount int
}

type NamedLock struct {
	lockMap map[string]*namedLockEntry
	lock    sync.Mutex
}

func NewNamedLock() *NamedLock {
	return &NamedLock{lockMap: make(map[string]*namedLockEntry)}
}

func (n *NamedLock) getLock(name string) *namedLockEntry {
	n.lock.Lock()
	defer n.lock.Unlock()
	entry, ok := n.lockMap[name]
	if !ok {
		entry = &namedLockEntry{}
		n.lockMap[name] = entry
	}
	entry.lockCount++
	return entry
}

func (n *NamedLock) Lock(name string) func() {
	entry := n.getLock(name)
	entry.lock.Lock()
	one := new(sync.Once)
	return func() {
		one.Do(func() {
			n.lock.Lock()
			defer n.lock.Unlock()
			entry.lock.Unlock()
			entry.lockCount--
			if entry.lockCount == 0 {
				delete(n.lockMap, name)
			}
		})
	}
}

type ContextLock struct {
	lockChan chan struct{}
}

func (c *ContextLock) Lock(ctx context.Context) func() {
	select {
	case <-c.lockChan:
		once := &sync.Once{}
		return func() {
			once.Do(func() {
				c.lockChan <- struct{}{}
			})
		}
	case <-ctx.Done():
		return nil
	}
}

func (c *ContextLock) LockTimeout(timeout time.Duration) func() {
	withTimeout, cancelFunc := context.WithTimeout(context.Background(), timeout)
	defer cancelFunc()
	return c.Lock(withTimeout)
}

func NewContextLock() *ContextLock {
	lock := &ContextLock{lockChan: make(chan struct{}, 1)}
	lock.lockChan <- struct{}{}
	return lock
}

type SubReaderSeeker struct {
	cur    int64
	offset int64
	size   int64
	f      io.ReadSeeker
}

func NewSubReaderSeeker(f io.ReadSeeker, offset int64, size int64) *SubReaderSeeker {
	return &SubReaderSeeker{
		f:      f,
		offset: offset,
		size:   size,
	}
}

func (s *SubReaderSeeker) Read(p []byte) (n int, err error) {
	available := s.size - s.cur
	if available == 0 {
		return 0, io.EOF
	}
	oldLen := len(p)
	if available < int64(oldLen) {
		p = p[:available]
	}
	n, err = s.f.Read(p)
	s.cur += int64(n)
	if err == nil && oldLen != len(p) {
		err = io.EOF
	}
	return
}

func (s *SubReaderSeeker) Seek(offset int64, whence int) (int64, error) {
	switch whence {
	case io.SeekCurrent:
		offset = s.cur + offset
		if offset >= s.size {
			return 0, io.ErrUnexpectedEOF
		}
	case io.SeekEnd:
		offset = s.size - offset
		if offset < 0 {
			return 0, io.ErrUnexpectedEOF
		}
	}
	s.cur = offset
	return s.f.Seek(s.offset+offset, io.SeekStart)
}

type BytesReadCloser struct {
	offset int
	data   []byte
}

func NewBytesReadCloser(data []byte, offset int) *BytesReadCloser {
	return &BytesReadCloser{
		data:   data,
		offset: offset,
	}
}

func (b *BytesReadCloser) Read(p []byte) (n int, err error) {
	n = len(b.data) - b.offset
	if n == 0 {
		return 0, io.EOF
	}
	if n < len(p) {
		err = io.EOF
		p = p[:n]
	} else {
		n = len(p)
	}
	copy(p, b.data[b.offset:])
	b.offset += n
	return
}

func (b *BytesReadCloser) Close() error {
	return nil
}

type BlockingQueue struct {
	requireChan list.List
	data        list.List
	lock        sync.Mutex
	ctx         context.Context
}

func (b *BlockingQueue) Push(v interface{}) error {
	b.lock.Lock()
	if b.requireChan.Len() == 0 {
		b.data.PushBack(v)
		b.lock.Unlock()
	} else {
		front := b.requireChan.Front()
		c := front.Value.(chan interface{})
		b.requireChan.Remove(front)
		b.lock.Unlock()
		select {
		case c <- v:
		case <-b.ctx.Done():
		}
	}
	return b.ctx.Err()
}

func (b *BlockingQueue) Pop(tmp chan interface{}) (interface{}, error) {
	b.lock.Lock()
	if b.data.Len() == 0 {
		b.requireChan.PushBack(tmp)
		b.lock.Unlock()
		select {
		case v := <-tmp:
			return v, nil
		case <-b.ctx.Done():
			return nil, b.ctx.Err()
		}
	} else {
		front := b.data.Front()
		v := front.Value
		b.data.Remove(front)
		b.lock.Unlock()
		return v, b.ctx.Err()
	}
}

func NewBlockingQueue(ctx context.Context) *BlockingQueue {
	return &BlockingQueue{
		ctx: ctx,
	}
}
